home *** CD-ROM | disk | FTP | other *** search
/ SuperHack / SuperHack CD.bin / CODING / CPP / METAKIT.ZIP / EXAMPLES / FTPCAT / FTPCAT.CPP next >
Encoding:
C/C++ Source or Header  |  1996-12-09  |  8.3 KB  |  287 lines

  1. //    ftpcat.cpp  -  ftp site catalog sample code
  2. //
  3. //    This is a part of the MetaKit library.
  4. //    Copyright (c) 1996 Meta Four Software.
  5. //    All rights reserved.
  6. /////////////////////////////////////////////////////////////////////////////
  7.  
  8. #include "m4kit.h"
  9.  
  10. #include <io.h>
  11.  
  12. /////////////////////////////////////////////////////////////////////////////
  13. // Property definitions
  14.  
  15.     c4_ViewProp        pFiles ("files");
  16.     c4_IntProp        pParent ("parent"),
  17.                     pSize ("size"),
  18.                     pDate ("date");
  19.     c4_StringProp    pName ("name");
  20.  
  21. /////////////////////////////////////////////////////////////////////////////
  22. // Reconstruct the full path name from a subdirectory index in the tree
  23.  
  24. CString fFullPath(c4_View& dirs_, int dirNum_)
  25. {
  26.         // Prefix all parent dir names until the root level is reached
  27.     CString path;
  28.     for (;;)
  29.     {
  30.         path = pName (dirs_[dirNum_]) + "/" + path;
  31.  
  32.         if (dirNum_ == 0)
  33.             return path; // this result always has a trailing backslash
  34.             
  35.         dirNum_ = (int) pParent (dirs_[dirNum_]);
  36.     }
  37. }
  38.  
  39. /////////////////////////////////////////////////////////////////////////////
  40. // Attempt to convert Unix date back to good ol' DOS format, i.e. 7:5:4 bits
  41.  
  42. int DecodeUnixDate(const CString& buf_)
  43. {
  44.     static CString months = "JanFebMarAprMayJunJulAugSepOctNovDec";
  45.  
  46.         // extra logic, in case no year was specified
  47.     static int limit = 0, year = 0;
  48.     if (!limit)
  49.     {
  50.             // determine the time 30 days ahead (clock may be a bit off)
  51.         time_t t1 = time(0) + 30 * 24 * 3600L;
  52.         tm* t2 = gmtime(&t1);
  53.             // remember that date as the limit date
  54.         limit = ((t2->tm_year - 80) << 9) |
  55.                 ((t2->tm_mon + 1) << 5) | t2->tm_mday;
  56.             // remember the default year to use if none is specified
  57.             // this may be a year to far, we'll back up later if needed
  58.         year = t2->tm_year + 1900;
  59.     }
  60.  
  61.     int m = months.Find(buf_.Left(3));
  62.     if (m % 3 != 0 || buf_[3] != ' ' || buf_[6] != ' ')
  63.         return 0;
  64.  
  65.     int y = buf_[9] == ':' ? year : atoi(buf_.Mid(8,4));
  66.     if (y < 1980 || y > 2080)
  67.         return 0;
  68.  
  69.     int x = ((y - 1980) << 9) | ((m / 3 + 1) << 5) | atoi(buf_.Mid(4,2));
  70.         // if the date is within a year into the future, use prev year
  71.     if (x > limit && x - 512 < limit)
  72.         x -= 512;
  73.     return x;
  74. }
  75.  
  76. /////////////////////////////////////////////////////////////////////////////
  77. // Decode a single line of a Unix-style directory listing
  78.  
  79.     // Note: there are a *lot* more systems and listing formats out there,
  80.     // but this is only an example, it works fine with several popular sites
  81.  
  82. char DecodeUnixEntry(const CString& buf_, c4_Row& entry_)
  83. {
  84.         // assume each entry ends with: ' ' <size> ' ' <date> ' ' <name>
  85.     int n = buf_.ReverseFind(' ');
  86.  
  87.         // Mac filenames can contain ' ', so check in regular place first
  88.     if (n > 55 && buf_[54] == ' ' && buf_[41] == ' ' &&
  89.                         DecodeUnixDate(buf_.Mid(42, 12)) != 0)
  90.         n = 54; // date in regular position is ok, so use that instead
  91.  
  92.     if (n > 40 && buf_[n-13] == ' ')
  93.     {
  94.         pName (entry_) = buf_.Mid(n+1);
  95.  
  96.         switch (buf_[0])
  97.         {
  98.         case '-':        // regular file
  99.                     pDate (entry_) = DecodeUnixDate(buf_.Mid(n-12, 12));
  100.                     n = buf_.Left(n-13).ReverseFind(' ');
  101.                     if (n > 20)
  102.                     {
  103.                         pSize (entry_) = atol(buf_.Mid(n));
  104.                         return 'f';
  105.                     }
  106.                     break;
  107.  
  108.         case 'd':        // directory, but not if name starts with '.'
  109.                     if (buf_[n+1] != '.')
  110.                         return 'd';
  111.         }
  112.     }
  113.  
  114.     return 0;
  115. }
  116.  
  117. /////////////////////////////////////////////////////////////////////////////
  118. // Scan a remote ftp site and return a corresponding structure for it
  119. //
  120. //    This code is extremely simple in terms of tcp/ip stuff, since there
  121. //    aren't any calls - everything is handled by Win95's FTP.EXE program.
  122. //
  123. //    On the other hand, this code is pretty tricky since two pipes are
  124. //    set up to control the execution of this child program. One pipe feeds
  125. //    commands to ftp, the other reads back results and decodes each line.
  126. //
  127. //    For some unknown reason, all server messages are lost. This is not
  128. //    critical, since the list output *does* get through, as well as any
  129. //    other output from commands. That's enough to make this thing work.
  130. //
  131. //    The bottom line is:
  132. //
  133. //        1)  There is no networking stuff in here, FTP.EXE does it all
  134. //        2)    This is built as a console task, so popen is neatly hidden
  135. //        3)    The result is a perfectly usable command-line utility
  136. //        4)    This only works on Win95 (perhaps also on NT 3.51 or 4.0)
  137. //        5)    The port suffix (site::port) is not implemented
  138.  
  139. c4_View fScanFTP(const char* path_, const char* usr_, const char* pw_)
  140. {
  141.         // everything before the first '/' is the site name
  142.     CString site = path_;
  143.     site = site.SpanExcluding("/");
  144.  
  145.         // start with a view containing the path (without the site prefix)
  146.     c4_View dirs;
  147.     dirs.Add(pName [path_ + site.GetLength()]);
  148.  
  149.         // set up a pipe, this reminds me of the good ol' Unix days...
  150.     int ph[2];
  151.     VERIFY(_pipe(ph, 100000, 0) == 0); // allow 100k in the pipeline
  152.     VERIFY(_dup2(ph[1], 1) == 0);
  153.     VERIFY(_close(ph[1]) == 0);
  154.         // now, when the "list" file is read, its data will come from stdout
  155.     FILE* list = fdopen(ph[0], "rt");
  156.     ASSERT(list);
  157.  
  158.     fprintf(stderr, "Connecting to %s ...\n", (const char*) site);
  159.  
  160.         // spawn a process, connect, and prepare to pipe commands to it
  161.     FILE* cmds = _popen("ftp -n " + site, "wt");
  162.     ASSERT(cmds);
  163.  
  164.         // only the child should have the write side of the pipe open
  165.     VERIFY(_dup2(2, 1) == 0);
  166.  
  167.         // prepare to do some work by logging in
  168.     fprintf(cmds, "user %s %s\npwd\n", usr_, pw_);
  169.     fflush(cmds);
  170.  
  171.         // read one line back right now, to make sure connection is ok
  172.     char buf [1024];
  173.     fgets(buf, sizeof buf, list);
  174.  
  175.         // This loop "automagically" handles the recursive traversal of all
  176.         // subdirectories. The trick is that each scan may add new entries
  177.         // at the end, causing this loop to continue (GetSize() changes!).
  178.     
  179.     int i;
  180.  
  181.     for (i = 0; i < dirs.GetSize(); ++i)
  182.     {
  183.         CString path = fFullPath(dirs, i);
  184.         if (path != "/")    // remove the trailing slash
  185.             path = path.Left(path.GetLength() - 1);
  186.  
  187.             // send two command to the child process, the second one is
  188.             // needed to produce a trailing line we can wait on
  189.         fprintf(stderr, "%4d: %-65.65s\r", i, (const char*) path);
  190.         fputs("dir " + path + "\npwd\n", cmds);
  191.         fflush(cmds);
  192.  
  193.         c4_View files;
  194.         c4_Row dir, file;
  195.  
  196.             // look at each of the returned lines in turn
  197.         while (fgets(buf, sizeof buf, list))
  198.         {
  199.             CString temp = buf;
  200.             temp = temp.SpanExcluding("\r\n");
  201.  
  202.             int result = atoi(temp);
  203.             if (result == 0)
  204.             {
  205. //                puts(temp);
  206.                 char type = DecodeUnixEntry(temp, file);
  207.                 if (type == 'd')
  208.                 {
  209.                     pParent (dir) = i;
  210.                     pName (dir) = pName (file);
  211.                     dirs.Add(dir);
  212.                 }
  213.                 else if (type == 'f')
  214.                 {
  215.                     files.Add(file);
  216.                 }
  217.             }
  218.             else
  219.             {
  220.                 pFiles (dirs[i]) = files.SortOn(pName);
  221.                 break;
  222.             }
  223.         }
  224.     }
  225.  
  226.     fprintf(stderr, "%75s\r%4d directories scanned.\n", "", i);
  227.  
  228.     _pclose(cmds);
  229.     fclose(list);
  230.     
  231.         // The returned object contains the entire directory tree.
  232.         // Everything is automatically destroyed when no longer referenced.    
  233.     return dirs;
  234. }
  235.  
  236. /////////////////////////////////////////////////////////////////////////////
  237. // Try this on internet: "ftpcat ftp.winsite.com/pub/pc/win95/programr"
  238.  
  239. int main(int argc, char** argv)
  240. {
  241.     const char* dest = argc == 3 ? argv[--argc] : "ftpcat.dat";
  242.  
  243.     if (argc != 2)
  244.     {
  245.         fprintf(stderr, "Usage: FTPCAT site/path [output]\n"
  246.             "   or: FTPCAT [ftp://][user:passwd@]site[/path] [output]\n");
  247.         return 1;
  248.     }
  249.  
  250.         // the following logic splits up an URL into its components
  251.  
  252.     CString arg = argv[1];
  253.     if (arg.Left(6).CompareNoCase("ftp://") == 0)
  254.         arg = arg.Mid(6);
  255.  
  256.     CString id = "anonymous:ftpcat@any.org";
  257.     if (arg.Find('@') >= 0)
  258.     {
  259.         int n = arg.ReverseFind('@'); // use the last one!
  260.         id = arg.Left(n);
  261.         arg = arg.Mid(n + 1);
  262.     }
  263.  
  264.     CString pass;
  265.     if (id.Find(':') >= 0)
  266.     {
  267.         int n = id.Find(':');
  268.         pass = id.Mid(n + 1);
  269.         id = id.Left(n);
  270.     }
  271.  
  272.         // ready to scan, prepare a storage object for the results
  273.     c4_Storage storage (dest, true);
  274.  
  275.         // this scans the ftp site and saves the results
  276.     c4_View view = fScanFTP(arg, id, pass);
  277.     storage.Store("dirs", view);
  278.  
  279.         // data will only be stored when actually comitted
  280.     storage.Commit();
  281.  
  282.     return 0;
  283. }
  284.  
  285. /////////////////////////////////////////////////////////////////////////////
  286. // $Id: ftpcat.cpp,v 1.2 1996/12/04 14:50:18 jcw Exp $
  287.